
import { ProtobufUtil } from './protobufUtil';
import { MessageMapByMsgName } from './Protobuf/protoMappings';
import loginPb = require('./Protobuf/loginPb');
import { PbCmd } from "./Protobuf/protoMappings";

let ws: WebSocket = null;
let heartbeatTimer: any = null;
let heartbeatResTimeoutTimer: any = null;

let handlers: { [cmd: string]: Function } = {};
let bindedObj: { [cmd: string]: any } = {};
let msgCache: { "id": string, "data": any }[] = [];
let openOrClose = { "open": "state_open", "close": "state_close" };
let tmpBuf = { "len": 0, "buffer": new Uint8Array(0) };
let heartbeat = 3; // 心跳3s跳一次


// 屏蔽显示在console上面输出的协议
export const BlockShowCmd = {
    [PbCmd.login_heart_c2s]: true,
    [PbCmd.login_heart_s2c]: true,
    [PbCmd.lock_step_one_frame_inputs_s2c]: true
};

export class network {
    /**
     * 连接服务器
     * @param host 
     * @param port 
     */
    static connect(host: string, port: number) {
        network.disconnect(true);
        tmpBuf.len = 0;
        tmpBuf.buffer = new Uint8Array(0);
        let url = "ws://" + host + ":" + port;
        ws = new WebSocket(url);
        ws.binaryType = 'arraybuffer';
        ws.onopen = function () {
            console.log(`连接服务器 ${host}:${port}成功`);
            network.addMsgCache(openOrClose.open);
            network.addHandler(PbCmd.login_heart_s2c, network.onLoginHeartS2C, this);
            heartbeatTimer = setInterval(sendHeartbeat, heartbeat * 1000);
        };

        ws.onerror = function () {
            console.log(`连接服务器 ${host}:${port}错误`);
        };

        ws.onclose = function () {
            console.log(`连接服务器 ${host}:${port}，连接被关闭`);
            network.closeNetwork("服务器断开了连接");
        };
        ws.onmessage = function (event) {
            handleMsg(new Uint8Array(event.data));
        };
    }

     // 服务器心跳返回
    static onLoginHeartS2C(message: loginPb.Game.login_heart_s2c) {
        // 收到心跳，清除超时断开连接定时器
        clearTimeout(heartbeatResTimeoutTimer);
        heartbeatResTimeoutTimer = null;
    }

    /**
     * 断开连接
     * @param clearMsg 是否清空消息队列，请开发者保持为 true
     */
    static disconnect(clearMsg: boolean = true) {
        if (ws) {
            ws.onopen = function () { };
            ws.onerror = function () { };
            ws.onclose = function () { };
            ws.onmessage = function () { };
            ws.close();
            ws = null;
            tmpBuf.len = 0;
            tmpBuf.buffer = new Uint8Array(0);
            clearInterval(heartbeatTimer);
            clearTimeout(heartbeatResTimeoutTimer);
            heartbeatResTimeoutTimer = null;
        }
        if (clearMsg) {
            msgCache.length = 0;
        }
    }

    static isConnected() {
        if (ws) return true;
        else return false;
    }

    /**
     * 添加网络连接成功的消息监听
     * @param cb 
     * @param self 
     */
    static onOpen(cb: (msg?: any) => void, self: any) {
        handlers[openOrClose.open] = cb.bind(self);
        bindedObj[openOrClose.open] = self;
    }

    /**
     * 移除网络连接成功的消息监听
     */
    static offOpen() {
        delete handlers[openOrClose.open];
        delete bindedObj[openOrClose.open];
    }

    /**
     * 添加网络断开的消息监听
     * @param cb 
     * @param self 
     */
    static onClose(cb: () => void, self: any) {
        handlers[openOrClose.close] = cb.bind(self);
        bindedObj[openOrClose.close] = self;
    }

    /**
     * 移除网络断开的消息监听
     */
    static offClose() {
        delete handlers[openOrClose.close];
        delete bindedObj[openOrClose.close];
    }

    /**
     * 添加消息监听
     * @param cmd 
     * @param cb 
     * @param self 
     */
    static addHandler(cmd: string, cb: (msg?: any) => void, self: any) {
        handlers[cmd] = cb.bind(self);
        bindedObj[cmd] = self;
    }

    /**
     * 移除绑定的消息监听
     * @param self 
     */
    static removeThisHandlers(self: any) {
        for (let cmd in bindedObj) {
            if (bindedObj[cmd] === self) {
                delete bindedObj[cmd];
                delete handlers[cmd];
            }
        }
    }

    /**
     * 添加消息缓存
     * @param self 
     */

    static addMsgCache(cmd, data = null) {
        msgCache.push({ "id": cmd, "data": data });
    }


    /**
     * 发送消息
     * @param cmd 
     * @param data 
     */
    static sendMsg(data?: any) {
        if (!ws || ws.readyState !== 1) {
            console.warn("ws is null");
            return;
        }
        if (data === undefined) {
            data = null;
        }
        let msgName = data.constructor.name;
        if (!BlockShowCmd[msgName]) {
            console.log(`发送消息 ${msgName}:`, data.toJSON());
        }
        let buffer = encode(data);
        ws.send(buffer);
    }

    /**
     * 读取消息
     */
    static readMsg() {
        if (msgCache.length > 0) {
            let tmp = msgCache.shift();
            if (handlers[tmp.id]) {
                handlers[tmp.id](tmp.data);
            }
        }
    }

    /**
     * 关闭网络
     */
    static closeNetwork(reason = "常规关闭网络") {
        console.warn("关闭网络 原因：", reason);
        network.disconnect(false);
        network.addMsgCache(openOrClose.close);
    }

}

// 数据封包
function encode(data: any) {
    try {
        const [cmdMainId, cmdSubId, _] = MessageMapByMsgName[data.constructor.name] as [number, number, string]
        let pbData = ProtobufUtil.pbEncode(data);
        // length - 16位, cmdMainId - 8位, cmdSubId - 8位, pbData - n位
        let msgLen = pbData.length + 4;
        const buffer = new ArrayBuffer(msgLen);
        const view = new DataView(buffer);

        // 写入大端序16位整数数据长度
        view.setUint16(0, msgLen, false);
        // 写入8位整数主协议号
        view.setUint8(2, cmdMainId);
        // 写入8位整数子协议号
        view.setUint8(3, cmdSubId);
        
        // 4. 写入动态长度的 Protobuf 数据（偏移4开始）
        const uint8View = new Uint8Array(buffer);

        uint8View.set(pbData, 4); // 将 pbData 复制到缓冲区第4字节的位置
        return buffer;
    } catch (e) {
        console.log(e);
    }
}

// 数据解包
function decode(buffer: ArrayBuffer) {
    try {
        const view = new DataView(buffer);
        // 1. 读取大端序的16位数据长度（但实际可能不需要用它，因为整个buffer已经是完整数据）
        const msgLen = view.getUint16(0, false);
        
        // 2. 验证数据完整性
        if (buffer.byteLength - 2 !== msgLen) {
            throw new Error(`Data length mismatch: Header says ${msgLen}, actual is ${buffer.byteLength - 2}`);
        }

        // 3. 读取主协议号和子协议号
        const cmdMainId = view.getUint8(2);
        const cmdSubId = view.getUint8(3);

        // 4. 提取 Protobuf 数据部分（从第4字节开始）
        const pbData = new Uint8Array(buffer, 4);

        // 5. 解码 Protobuf 数据
        const data = ProtobufUtil.pbDecode(cmdMainId, cmdSubId, pbData);

        return data;
    } catch (e) {
        console.error("解包错误", e);
        throw e; // 或返回默认值
    }
}

function handleMsg(data: Uint8Array) {
    try {
        let structMsg = decode(data.buffer);
        let msgName = structMsg.constructor.name;
        if (!BlockShowCmd[msgName]) {
            console.log(`收到消息 ${msgName}:`, structMsg.toJSON());
        }
        network.addMsgCache(structMsg.constructor.name, structMsg);
    } catch (e) {
        console.log(`收到消息错误 ${e}`);
    }
}


function sendHeartbeat() {
    // 心跳
    let message = loginPb.Game.login_heart_c2s.create();
    network.sendMsg(message);

    // 设置心跳5秒就超时断开网络
    if (heartbeatResTimeoutTimer === null) {
        heartbeatResTimeoutTimer = setTimeout(function () {
            network.closeNetwork("心跳超时");
        }, 5 * 1000);
    }
}

